Skip to content

feat: add retry utility for network operations - #2

Closed
benaiad wants to merge 2 commits into
mainfrom
test/trigger-review-fix
Closed

feat: add retry utility for network operations#2
benaiad wants to merge 2 commits into
mainfrom
test/trigger-review-fix

Conversation

@benaiad

@benaiad benaiad commented Apr 27, 2026

Copy link
Copy Markdown
Owner

Adds a generic retry helper with configurable backoff and a Retry-After header parser.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR adds a retry utility with backoff and a Retry-After header parser. There is an off-by-one bug in the retry loop (<= instead of <) that causes one more attempt than maxAttempts specifies. Additionally, parseRetryAfter lacks input validation and can return NaN, and RetryOptions uses number instead of the safer number | undefined pattern for the partial merge. Risk level: medium — the off-by-one is a correctness bug that will surprise callers.

4 finding(s): 1 error, 2 warning, 1 info

[ERROR] correctness

File: src/utils/retry.ts:18

Off-by-one error: the loop condition i <= opts.maxAttempts iterates maxAttempts+1 times. For maxAttempts=3, the function body runs 4 times (i=0,1,2,3), meaning 4 attempts instead of the documented 3.

Suggestion: Change the loop condition to i < opts.maxAttempts so it runs exactly maxAttempts times.


[WARNING] error-handling

File: src/utils/retry.ts:34

parseRetryAfter returns NaN (a negative number in practice) when the header value is not a valid number or parseable date. Callers receive a useless delay value with no indication of failure.

Suggestion: Return a sensible default (e.g., 0 or throw) when parsing fails, or wrap the date parsing in a try-catch and validate with isNaN before returning.


[WARNING] error-handling

File: src/utils/retry.ts:35

When new Date(header) produces an invalid date, date.getTime() returns NaN, so the return value becomes NaN. Subtracting Date.now() from NaN yields NaN.

Suggestion: After creating the Date, check isNaN(date.getTime()) and fall back to a default or throw an error.


[INFO] correctness

File: src/utils/retry.ts:37

parseRetryAfter can return a negative number if the parsed date is in the past. A negative delay passed to setTimeout will fire immediately, which may not be the desired behavior.

Suggestion: Clamp the return value to a minimum of 0: return Math.max(0, date.getTime() - Date.now()).


Reviewed by pi-relay · View full run

Comment thread src/utils/retry.ts
export async function retry<T>(fn: () => Promise<T>, options: Partial<RetryOptions> = {}): Promise<T> {
const opts = { ...DEFAULT_OPTIONS, ...options };
let lastError: any;
let delay = opts.delayMs;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ERROR] correctness

Off-by-one error: the loop condition i <= opts.maxAttempts iterates maxAttempts+1 times. For maxAttempts=3, the function body runs 4 times (i=0,1,2,3), meaning 4 attempts instead of the documented 3.

Suggestion: Change the loop condition to i < opts.maxAttempts so it runs exactly maxAttempts times.

Comment thread src/utils/retry.ts Outdated
}

export function parseRetryAfter(header: string): number {
const val = parseInt(header);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[WARNING] error-handling

parseRetryAfter returns NaN (a negative number in practice) when the header value is not a valid number or parseable date. Callers receive a useless delay value with no indication of failure.

Suggestion: Return a sensible default (e.g., 0 or throw) when parsing fails, or wrap the date parsing in a try-catch and validate with isNaN before returning.

Comment thread src/utils/retry.ts Outdated

export function parseRetryAfter(header: string): number {
const val = parseInt(header);
if (val) return val * 1000;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[WARNING] error-handling

When new Date(header) produces an invalid date, date.getTime() returns NaN, so the return value becomes NaN. Subtracting Date.now() from NaN yields NaN.

Suggestion: After creating the Date, check isNaN(date.getTime()) and fall back to a default or throw an error.

Comment thread src/utils/retry.ts Outdated
const val = parseInt(header);
if (val) return val * 1000;
const date = new Date(header);
return date.getTime() - Date.now();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[INFO] correctness

parseRetryAfter can return a negative number if the parsed date is in the past. A negative delay passed to setTimeout will fire immediately, which may not be the desired behavior.

Suggestion: Clamp the return value to a minimum of 0: return Math.max(0, date.getTime() - Date.now()).

@github-actions

Copy link
Copy Markdown

AI Review: Fixes Applied

Reviewed and found 4 issue(s). All addressed.

Changes Made

Fixed in src/utils/retry.ts:

  • Error: Off-by-one in retry loop: changed i <= opts.maxAttempts to i < opts.maxAttempts so it runs exactly maxAttempts times instead of maxAttempts+1.
  • Warning: parseRetryAfter now handles invalid input: returns 0 when parseInt yields NaN or when Date parsing fails (invalid date string), wraps date parsing in try-catch, and clamps negative delays to 0 via Math.max(0, ...).
  • Also fixed linter issues: added radix 10 to parseInt, used Number.isNaN instead of global isNaN.

npm run check passes cleanly.

Verification passed.


View full run

@benaiad

benaiad commented Apr 27, 2026

Copy link
Copy Markdown
Owner Author

Test PR — verified the review → findings → fix → push flow. Closing.

@benaiad benaiad closed this Apr 27, 2026
@benaiad
benaiad deleted the test/trigger-review-fix branch April 27, 2026 06:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant